﻿/******************************************************
* author :  cwj
* email  :  chenwenji_360@live.com 
* history:  created by cwj 2015/7/24 22:56:05 
* clrversion :4.0.30319.18444
******************************************************/

using Machine.DataAccess.Common.ORM;
using Machine.DataAccess.Linq.Relation;
using Machine.DataAccess.Operation;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace Machine.DataAccess.Linq
{
    abstract class SetFormatBase : DbVisitor
    {
        protected StringBuilder build = new StringBuilder();
        protected List<DbParameter> parameter = new List<DbParameter>();
        protected DbProviderFactory factory;//= DbProviderFactories.GetFactory(Config.Instance.DefaultProvider.Provider);
        protected ColumExpression currentColum;
        protected bool reNameColum = false;
        protected ProviderElement ProviderElement { get; private set; }

        public SetFormatBase(ProviderElement providerElement)
        {
            this.factory = DbProviderFactories.GetFactory(providerElement.Provider);
            this.ProviderElement = providerElement;
        }

        public TranslateResult Format(Expression exp)
        {
            this.Visit(exp);
            return new TranslateResult(this.build.ToString(), this.parameter.ToArray());
            //return build.ToString();
        }

        protected override Expression VisitTable(TableExpression table)
        {
            this.build.Append("`");
            this.build.Append(table.TableName);
            this.build.Append("`");
            this.build.Append(" as ");
            this.build.Append(table.TableArg);
            return table;
        }

        protected override Expression VisitSource(Expression source)
        {
            switch ((ProjectionType)source.NodeType)
            {
                case ProjectionType.Table:
                    this.VisitTable(source as TableExpression);
                    break;
                case ProjectionType.Select:
                    var select = source as SelectExpression;
                    this.build.Append("(");
                    this.VisitSelect(select);
                    this.build.Append(")");
                    this.build.Append(" as ");
                    this.build.Append(select.TableArg);
                    break;
                case ProjectionType.Join:
                    var join = source as JoinExpression;
                    //this.build.Append("(");
                    this.VisitSource(join.Left);
                    this.build.Append(" inner join ");
                    this.VisitSource(join.Right);
                    //this.build.Append(") ");
                    this.build.Append(" on ");
                    this.Visit(join.On);
                    break;
                //case ProjectionType.OrderBy:
                //    this.VisitOrderBy(source as OrderByExpression);
                case ProjectionType.GeoDistance:
                    this.VisitGeoDistance((GeoDistanceExpression)source);
                    break;
                default:
                    throw new NotImplementedException("不支持的类型");
            }
            return source;
        }

        protected override Expression VisitSelect(SelectExpression select)
        {
            if(select.IsDelete)
            {
                this.build.Append("Delete ");
            }
            if (select.IsCount)
            {
                this.build.Append("Select Count(*) From ( ");
            }

            this.build.Append("Select ");
            if (select.IsDistinct)
            {
                this.build.Append(" Distinct ");
            }
            if (select.Top > 0)
            {
                this.build.Append("Top(");
                this.build.Append(select.Top);
                this.build.Append(") ");
            }
            for (int i = 0; i < select.Colums.Count(); i++)
            {
                reNameColum = true;
                if (i > 0) this.build.Append(",");
                var colum = select.Colums.ElementAt(i);
                var columExpression = this.Visit(colum.ColumExpression) as ColumExpression;
                if (columExpression == null || columExpression.Name != colum.ColumName)
                {
                    this.build.Append(" as ");
                    this.build.Append(columExpression.Name);
                }
                reNameColum = false;
            }
            //if (select.Colums.Count() == 0)
            //{
            //    this.build.Append(" Count(*) ");
            //}
            if (select.From != null || select.JoinSingleType.Count() > 0)
            {
                //this.build.Append(" From ");
                //this.VisitSource(select.From);
                //if (select.JoinSingleType.Count() > 0)
                //{
                //}
                this.build.Append(" From ");
                var tableExpression = select.From as TableExpression;
                this.VisitSource(tableExpression);
                if (select.JoinSingleType.Count() > 0)
                {
                    VistJoinSingleType(select, tableExpression);
                }
            }
            if (select.Where != null || select.Skip > 0)
            {
                this.build.Append(" Where ");
                this.Visit(select.Where);
                if (select.Skip > 0) this.VisitSkip(select);
            }
            if (select.Like != null)
            {
                if (select.Where != null) this.build.Append(" And ");
                else this.build.Append(" Where ");
                this.Visit(select.Like);
            }
            if (select.OrderBy != null && select.OrderBy.Count() > 0 && select.Colums.Count() > 0)
            {
                this.build.Append(" Order By ");

                for (int i = 0; i < select.OrderBy.Count(); i++)
                {
                    if (i > 0) this.build.Append(",");
                    var item = select.OrderBy.ElementAt(i).ColumExpression as ColumExpression;
                    this.build.Append(item.TableArg);
                    this.build.Append(".");
                    this.build.Append(item.Name);
                }
                this.build.Append(" ");
                this.build.Append(select.OrderByType);
            }

            if (select.IsCount)
            {
                this.build.Append(" )  as t0");
            }
            return select;
        }

        [Obsolete]
        protected virtual void VistJoinSingleType(SelectExpression select, TableExpression tableExpression)
        {
            foreach (var item in select.JoinSingleType)
            {
                DataSchemCreatorBase creator = Config.Instance.GetDbSchemCreator(this.ProviderElement);
                DbSchemItem schem = null;// Config.Instance.GetDbSchemInfo(this.ProviderElement).GetSchem(item.Type);
                schem = Config.Instance.GetDbSchemInfo(this.ProviderElement).GetSchem(tableExpression.Type);

                this.build.Append(" left join ");
                this.VisitSource(item);
                this.build.Append(" on ");

                if (!schem.ForeignKeys.ContainsKey(item.TableName))
                {
                    foreach (var innnerItem in select.JoinSingleType)
                    {
                        schem = Config.Instance.GetDbSchemInfo(this.ProviderElement).GetSchem(innnerItem.Type);
                        if (schem.ForeignKeys.ContainsKey(item.TableName))
                        {
                            var fk = schem.ForeignKeys[item.TableName];
                            this.build.Append(string.Format(" {0}.{1} = {2}.{3} ",
                                innnerItem.TableArg, fk.From, item.TableArg, fk.To));
                        }
                    }
                }
                else
                {
                    var fk = schem.ForeignKeys[item.TableName];
                    this.build.Append(string.Format(" {0}.{1} = {2}.{3} ",
                        tableExpression.TableArg, fk.From, item.TableArg, fk.To));
                }

                //string outterName = null;
                //var relation = creator.GetTableRelationType(tableExpression.Type, item.Type);
                //switch (relation)
                //{
                //    case TableRelationType.Zero2Zero:
                //        continue;
                //    case TableRelationType.Zero2One:
                //    case TableRelationType.Zero2Many:
                //    case TableRelationType.One2One:
                //    case TableRelationType.Many2Zero:
                //    case TableRelationType.Many2One:
                //        schem = Config.Instance.GetDbSchemInfo(this.ProviderElement).GetSchem(item.Type);
                //        outterName = tableExpression.TableName;
                //        //this.CreateOneToOneRealtion(item.PKType, item.FKType);
                //        break;
                //    case TableRelationType.Many2Many:
                //        throw new Exception("未知异常");
                //    //this.CreateManyToManyRealtion(item.PKType, item.FKType);
                //    case TableRelationType.One2Zero:
                //    case TableRelationType.One2Many:
                //        schem = Config.Instance.GetDbSchemInfo(this.ProviderElement).GetSchem(tableExpression.Type);
                //        outterName = item.TableName;
                //        //this.CreateOneToOneRealtion(item.FKType, item.PKType);
                //        break;
                //    default:
                //        break;
                //}

                //if (!schem.ForeignKeys.ContainsKey(outterName))
                //{
                //    foreach (var innnerItem in select.JoinSingleType)
                //    {
                //        schem = Config.Instance.GetDbSchemInfo(this.ProviderElement).GetSchem(innnerItem.Type);
                //        if (schem.ForeignKeys.ContainsKey(item.TableName))
                //        {
                //            var fk = schem.ForeignKeys[item.TableName];
                //            this.build.Append(string.Format(" {0}.{1} = {2}.{3} ",
                //                innnerItem.TableArg, fk.From, item.TableArg, fk.To));
                //            //if (!schem.ForeignKeys.ContainsKey(tableExpression.TableName)) throw new Exception("数据库没有这个外键");
                //            //var fk = schem.ForeignKeys[tableExpression.TableName];
                //            if (relation == TableRelationType.One2Zero || relation == TableRelationType.One2Many)
                //            {
                //                this.build.Append(string.Format(" {0}.{1} = {2}.{3} ",
                //                item.TableArg, fk.To, tableExpression.TableArg, fk.From));
                //            }
                //            else
                //            {
                //                this.build.Append(string.Format(" {0}.{1} = {2}.{3} ",
                //                item.TableArg, fk.From, tableExpression.TableArg, fk.To));
                //            }
                //        }
                //    }
                //}
                //else
                //{
                //    var fk = schem.ForeignKeys[outterName];
                //    //if (!schem.ForeignKeys.ContainsKey(tableExpression.TableName)) throw new Exception("数据库没有这个外键");
                //    //var fk = schem.ForeignKeys[tableExpression.TableName];
                //    if (relation == TableRelationType.One2Zero || relation == TableRelationType.One2Many)
                //    {
                //        this.build.Append(string.Format(" {0}.{1} = {2}.{3} ",
                //        item.TableArg, fk.To, tableExpression.TableArg, fk.From));
                //    }
                //    else
                //    {
                //        this.build.Append(string.Format(" {0}.{1} = {2}.{3} ",
                //        item.TableArg, fk.From, tableExpression.TableArg, fk.To));
                //    }
                //}
                //throw new Exception("数据库没有这个外键");

            }
        }

        protected virtual void VistJoinSingleType(TableExpression primaryTable, IEnumerable<TableExpression> joinTypeCache)
        {
            var schem = Config.Instance.GetDbSchemInfo(this.ProviderElement).GetSchem(primaryTable.Type);
            foreach (var item in schem.ForeignKeys)
            {
                var containTable = joinTypeCache.FirstOrDefault(x => x.TableName == item.Key && x.DeclareType.GetTableName() == primaryTable.TableName);
                if (containTable == null) continue;
                this.build.Append(" left join ");

                //if (isVisitSource) this.VisitSource(primaryTable);
                this.VisitSource(containTable);

                this.build.Append(" on ");

                var fk = schem.ForeignKeys[containTable.TableName];
                this.build.Append(string.Format(" {0}.{1} = {2}.{3} ",
                    primaryTable.TableArg, fk.From, containTable.TableArg, fk.To));

                this.VistJoinSingleType(containTable, joinTypeCache);
            }
        }

        protected virtual void VisitSkip(SelectExpression select)
        {
            if (select.Where != null)
            {
                this.build.Append(" And ");
            }
            var colum = select.OrderBy.FirstOrDefault();
            this.build.Append(string.Format("{0}.{1}", colum.ColumExpression.TableArg, colum.ColumName));
            this.build.Append(" Not In ");
            this.build.Append(string.Format("(Select top {0} {1} From {2} Order By {1} {3})",
                select.Skip,
                colum.ColumName,
                (select.From as TableExpression).TableName,
                select.OrderByType));
        }

        protected override Expression VisitColum(ColumExpression colum)
        {
            if (colum.ColumType == ColumType.Max) this.build.Append(string.Format("Max({0}.{1})", colum.TableArg, colum.Name));
            if (colum.ColumType == ColumType.Min) this.build.Append(string.Format("Min({0}.{1})", colum.TableArg, colum.Name));
            if (colum.ColumType == ColumType.Sum) this.build.Append(string.Format("Sum({0}.{1})", colum.TableArg, colum.Name));
            if (colum.ColumType != ColumType.None) return colum;

            //if(colum.ColumType == ColumType.None) this.build.Append(string.Format("({0}.{1} as {3}{2}{1})", colum.TableArg, colum.Name, colum.DeclareTypeName, colum.ParentTypeName));
            if (reNameColum)
                this.build.Append(string.Format("{0}.{1} as {3}{2}{1}", colum.TableArg, colum.Name, colum.DeclareTypeName, colum.ParentTypeName));
            else
                this.build.Append(string.Format("{0}.{1}", colum.TableArg, colum.Name));

            //if (colum.ColumType == ColumType.None) this.build.Append(string.Format(") as {0}", colum.Name));
            this.currentColum = colum;
            return colum;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            var randomName = Guid.NewGuid().ToString().Replace("-", "").Substring(0, 6);
            this.build.Append(string.Format("@{0}{1}{2}", this.currentColum.DeclareTypeName, this.currentColum.Name,randomName));
            var p = factory.CreateParameter();
            p.ParameterName = string.Format("@{0}{1}{2}", this.currentColum.DeclareTypeName, this.currentColum.Name,randomName);//this.currentColum.Name;
            if (isLike == false)
            {
                p.Value = c.Value;
            }
            else
            {
                p.Value = string.Format("{0}%", c.Value);
            }
            parameter.Add(p);
            //if (parameter.FirstOrDefault(x => x.ParameterName == p.ParameterName) == null)
            //    parameter.Add(p);
            //parameter.Add(new SqlParameter(this.currentColum.Name, c.Value));
            return c;
        }
        protected override Expression VisitBinary(BinaryExpression b)
        {
            build.Append("(");
            this.Visit(b.Left);
            ConstantExpression right = null;
            if (b.Right.NodeType == ExpressionType.Convert)
            {
                right = (b.Right as UnaryExpression).Operand as ConstantExpression;
            }
            else
            {
                right = b.Right as ConstantExpression;
            }
            switch (b.NodeType)
            {
                case ExpressionType.AndAlso:
                case ExpressionType.And:
                    build.Append(" AND ");
                    break;
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                    build.Append(" OR ");
                    break;
                case ExpressionType.Equal:
                    if (isLike)
                    {
                        build.Append(" Like ");
                    }
                    else
                    {
                        if (right.Value == null) build.Append(" is null ");
                        else build.Append(" = ");
                    }
                    break;
                case ExpressionType.NotEqual:
                    {
                        if ((right as ConstantExpression).Value == null) build.Append(" is not null ");
                        else build.Append(" <> ");
                        break;
                    }
                case ExpressionType.LessThan:
                    build.Append(" < ");
                    break;
                case ExpressionType.LessThanOrEqual:
                    build.Append(" <= ");
                    break;
                case ExpressionType.GreaterThan:
                    build.Append(" > ");
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    build.Append(" >= ");
                    break;
                default:
                    throw new NotSupportedException(string.Format("方法{0}不支持", b.NodeType));
            }
            if (right != null && right.Value == null)
            {
                build.Append(")");
                return b;
            }
            else this.Visit(b.Right);
            build.Append(")");
            //if (b.Right is ConstantExpression && (b.Right as ConstantExpression).Value != null)
            //{
            //}
            return b;
        }

        bool isLike = false;
        protected override Expression VisitLike(LikeExpression like)
        {
            isLike = true;
            var expression = base.VisitLike(like);
            isLike = false;
            return expression;
        }

        protected virtual Expression VisitGeoDistance(GeoDistanceExpression geo)
        {
            return geo;
        }

        protected override Expression VisitUpdate(UpdateExpression update)
        {
            this.build.Append(" SET ");
            var set = update.Set as BinaryExpression;
            if (set == null) throw new Exception("错误的类型");
            this.Visit(set.Left);
            ConstantExpression right = null;
            if (set.Right.NodeType == ExpressionType.Convert)
            {
                right = (set.Right as UnaryExpression).Operand as ConstantExpression;
            }
            else
            {
                right = set.Right as ConstantExpression;
            }
            if (set.NodeType != ExpressionType.Equal) throw new Exception("不允许非相等的类型");
            this.build.Append(" = ");
            if (right != null && right.Value == null)
            {
                return set;
            }
            else this.Visit(set.Right);
            return update;
        }
    }

    class ReaderFormat : SetFormatBase
    {
        public ReaderFormat(ProviderElement providerElement)
            : base(providerElement)
        {
            this.schem = Config.Instance.GetDbSchemInfo(this.ProviderElement);
            this.creator = Config.Instance.GetDbSchemCreator(this.ProviderElement);
        }

        #region ManyToMany

        private Dictionary<string, Item> tableCache;//= new Dictionary<string, Item>();
        private int tableNum;
        //StringBuilder build = new StringBuilder();
        int bigTypeNum;
        Type bigType;
        Type collectionType;
        Tuple<string, DbForeignKeyInfo[]> outterFK;
        DbSchemInfoBase schem;
        DataSchemCreatorBase creator;
        TableRelationType relation;

        private class Item
        {
            public DbSchemItem Schem { get; set; }
            public int TableNum { get; set; }
            public Type Type { get; set; }
        }

        private void BindCollectionJoin()
        {
            #region Old
            //foreach (var keyValue in tableCache)
            //{
            //    if (keyValue.Key == bigType.GetTableName()) continue;
            //    foreach (var fk in keyValue.Value.Schem.ForeignKeys)
            //    {
            //        if (fk.Value.Table == bigType.GetTableName() || fk.Value.Table == collectionType.GetTableName()) continue;

            //        var outertable = tableCache[fk.Key];

            //        build.Append(string.Format(" Left Join {0} as t{1}", outertable.Schem.TableName, outertable.TableNum));
            //        build.Append(string.Format(" On t{0}.{1} = t{2}.{3} ", outertable.TableNum, fk.Value.To,
            //            keyValue.Value.TableNum, fk.Value.From));
            //    }
            //}

            //var outterTableNum = tableCache.ContainsKey(outterFK.Item1) ? tableCache[outterFK.Item1].TableNum :
            //    this.tableNum;

            //var outterCache = outterFK.Item2.ToDictionary(x => x.Table);

            //Item tableTemp;// = tableCache[outterFK.Item2[0].Table];
            //DbForeignKeyInfo fkTemp;// = outterFK.Item2[0];

            //if (outterCache.ContainsKey(collectionType.GetTableName()))
            //{
            //    fkTemp = outterCache[collectionType.GetTableName()];
            //    tableTemp = tableCache[fkTemp.Table];
            //    build.Append(string.Format(" Join {0} as t{1}", outterFK.Item1.ToLower(), tableNum));
            //    build.Append(string.Format(" On t{0}.{1} = t{2}.{3}", outterTableNum, fkTemp.From,
            //    tableTemp.TableNum, fkTemp.To));
            //}

            //if (outterCache.ContainsKey(bigType.GetTableName()))
            //{
            //    fkTemp = outterCache[bigType.GetTableName()];
            //    tableTemp = tableCache[fkTemp.Table];

            //    build.Append(string.Format(" Join {0} as t{1}", fkTemp.Table, tableTemp.TableNum));
            //    build.Append(string.Format(" On t{0}.{1} = t{2}.{3}", outterTableNum, fkTemp.From,
            //        tableTemp.TableNum, fkTemp.To));
            //}
            #endregion
            GetFK(bigType);
            var collectionTableTemp = tableCache[collectionType.GetTableName()];
            //bool isSingleReference = false;
            foreach (var fk in outterFK.Item2)
            {
                var temp = tableCache[fk.Table];
                if (bigType.GetTableName() != fk.Table)
                {
                    build.Append(string.Format(" left join {0} as t{1}", temp.Schem.TableName, temp.TableNum));
                    build.Append(string.Format(" on t{2}.{1} = t{0}.{3}",
                        temp.TableNum, fk.From, collectionTableTemp.TableNum, collectionTableTemp.Schem.PrimaryKeyName));
                }
                else
                {
                    build.Append(string.Format(" left join {0} as t{1}", temp.Schem.TableName, this.tableNum));
                    build.Append(string.Format(" on t{2}.{1} = t{0}.{3}",
                        this.tableNum, fk.From, collectionTableTemp.TableNum, collectionTableTemp.Schem.PrimaryKeyName));
                    this.tableNum++;
                    temp.TableNum++;
                }
            }
            //if (outterFK.Item1.Equals(collectionType.GetTableName()) && tableCache.ContainsKey(outterFK.Item1))
            //{
            //}
            var bigTableTemp = tableCache[bigType.GetTableName()];
            build.Append(string.Format(" Where t{0}.{1} = @{1}", bigTableTemp.TableNum, bigTableTemp.Schem.PrimaryKeyName));
        }

        private void BindCollectionSelect()
        {
            int index = 0;
            foreach (var keyValue in tableCache)
            {
                //if (keyValue.Key == bigType.GetTableName()) continue;
                var properties = keyValue.Value.Type.GetProperties();
                foreach (var property in properties)
                {
                    if (Type.GetTypeCode(property.PropertyType) != TypeCode.Object || property.PropertyType == typeof(Guid))
                    {
                        if (index > 0) build.Append(", ");
                        build.Append(string.Format("t{0}.{1} as {2}{1}",
                            keyValue.Value.TableNum, property.Name, keyValue.Value.Type.Name.ToLower()));
                        index++;
                    }
                }
            }
        }

        private void GetFK(Type type)
        {
            //if (type == bigType) this.bigTypeNum++;
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ITable<>)) return;
            if (tableCache.ContainsKey(type.GetTableName())) return;
            var schem = this.schem.GetSchem(type);
            tableCache[type.GetTableName()] = new Item() { Schem = schem, TableNum = this.tableNum++, Type = type };
            var properties = type.GetProperties();
            foreach (var property in properties)
            {
                var propertyType = property.PropertyType;
                if (Type.GetTypeCode(propertyType) == TypeCode.Object && propertyType != typeof(Guid))
                {
                    GetFK(propertyType);
                }
            }
        }

        public virtual TranslateResult Format(object obj, Type collectionType)
        {
            tableCache = new Dictionary<string, Item>();

            this.bigType = obj.GetType();
            this.collectionType = collectionType;
            this.relation = schem.GetTableRelationType(bigType, collectionType);
            GetFK(collectionType);
            //GetFK(bigType);

            outterFK = schem.GetOuterFKey(bigType, collectionType);

            build.Append("Select ");

            BindCollectionSelect();

            build.Append(" From ");

            build.Append(string.Format(" {0} as t{1} ", tableCache[collectionType.GetTableName()].Schem.TableName, tableCache[collectionType.GetTableName()].TableNum));

            BindCollectionJoin();

            var properties = DynamicAssignment.Instance.GetDictionary(bigType);
            var pk = tableCache[bigType.GetTableName()];
            var parameter = factory.CreateParameter();
            parameter.ParameterName = string.Format("@{0}", pk.Schem.PrimaryKeyName);
            parameter.Value = properties[pk.Schem.PrimaryKeyName.ToLower()].GetValue(obj);
            this.parameter.Add(parameter);

            return new TranslateResult(this.build.ToString(), this.parameter);
        }
        #endregion
    }
}
